import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.nio.ByteBuffer;
import java.security.AlgorithmParameterGenerator;
import java.security.AlgorithmParameters;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Random;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyAgreement;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.interfaces.DHPublicKey;
import javax.crypto.spec.DHParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.encoders.Base64;

public class InstantMessageServer
{
	DatagramSocket serverSocket;

	BigInteger p512;
	BigInteger g512;

	DHParameterSpec dhSpec;
	
	// clients who is in the process of building connection
	ArrayList<Client> pendingClients = new ArrayList<Client>();
	// clients who is already login in
	ArrayList<Client> establishedClients = new ArrayList<Client>();

	Object lock1 = new Object();
	Object lock2 = new Object();
	Random rand = new Random(System.currentTimeMillis());
	Cipher encodePrivateCipher;
	Cipher decodePrivateCipher;

	int port;

	// map user name with password
	HashMap<String, String> usrpassword;

	public InstantMessageServer(int p, byte[] key, HashMap<String, String> psw)
	{
		usrpassword = psw;
		port = p;

		PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(Base64.decode(key));
		KeyFactory kf;
		try
		{
			kf = KeyFactory.getInstance("RSA");
			PrivateKey privatek = kf.generatePrivate(privateKeySpec);
			encodePrivateCipher = Cipher.getInstance("RSA/NONE/PKCS1Padding");
			encodePrivateCipher.init(Cipher.ENCRYPT_MODE, privatek);

			decodePrivateCipher = Cipher.getInstance("RSA/NONE/PKCS1Padding");
			decodePrivateCipher.init(Cipher.DECRYPT_MODE, privatek);

			serverSocket = new DatagramSocket(port);

			AlgorithmParameterGenerator servAPG = AlgorithmParameterGenerator.getInstance("DH");
			servAPG.init(512);
			DHParameterSpec servParamSpec;
			servParamSpec = servAPG.generateParameters().getParameterSpec(DHParameterSpec.class);
			p512 = servParamSpec.getP();
			g512 = servParamSpec.getG();

		} catch (Exception e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

	}

	
	static enum ClientStatus
	{
		WaitLoginChallenge, WaitLoginResponse, WaitLoginSttart, Connnected
	};

	public DHPublicKey generateDHPubKey(KeyPair servKP)
	{
		DHPublicKey cliServPub = null;
		try
		{

			cliServPub = (DHPublicKey) KeyFactory.getInstance("DH").generatePublic(new X509EncodedKeySpec(servKP.getPublic().getEncoded()));
		} catch (Exception e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return cliServPub;
	}

	public byte[] generateDHKey(DHPublicKey servCliPub, KeyPair servKP)
	{
		try
		{

			DHParameterSpec servParamSpec = new DHParameterSpec(p512, g512);
			KeyAgreement servKA = KeyAgreement.getInstance("DH");
			servKA.init(servKP.getPrivate(), servParamSpec);
			servKA.doPhase(servCliPub, true);

			return servKA.generateSecret();
		} catch (Exception e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return null;
	}

	// find the client in the pending clients list or connected clients list based on the ip and type
	private Client getClient(InetAddress ip, int type)
	{
		ArrayList<Client> clients;
		Object lock;
		if (type == 1)
		{
			clients = pendingClients;
			lock = lock1;
		} else
		{
			clients = establishedClients;
			lock = lock2;
		}
		synchronized (lock)
		{
			for (int i = 0; i < clients.size(); i++)
			{
				if (clients.get(i).IPAddress.equals(ip))
				{
					return clients.get(i);
				}
			}
		}
		return null;
	}

	// remove the client the the client list
	private void removeClient(InetAddress ip, int type)
	{
		ArrayList<Client> clients;
		Object lock;
		if (type == 1)
		{
			clients = pendingClients;
			lock = lock1;
		} else
		{
			clients = establishedClients;
			lock = lock2;
		}

		synchronized (lock)
		{
			for (int i = 0; i < clients.size(); i++)
			{
				if (clients.get(i).IPAddress.equals(ip))
				{
					clients.remove(i);
				}
			}
		}
	}

	// add client to the clients list
	private void addClient(Client c, int type)
	{
		ArrayList<Client> clients;
		Object lock;
		if (type == 1)
		{
			clients = pendingClients;
			lock = lock1;
		} else
		{
			clients = establishedClients;
			lock = lock2;
		}

		synchronized (lock)
		{
			if (c.IPAddress != null)
			{
				this.removeClient(c.IPAddress, type);
			}

			Client c2 = this.getEstablishedClientFromByName(c.name);
			if (c2 != null && c2.IPAddress != null)
			{
				this.sendBroadLogOff(c2, c2.name);
			}
			clients.add(c);
		}
	}

	// handle Login Message
	void handelLoginMsg(byte[] d, DatagramPacket receivePacket)
	{
		System.out.println("handle Login msg");

		byte[] data = d;
		int length = data.length;

		InetAddress IPAddress = receivePacket.getAddress();
		Integer port = receivePacket.getPort();

		Client client = getClient(IPAddress, 1);

		// if the client already in the client list;
		if (client != null)
		{
			removeClient(IPAddress, 1);
		}

		client = new Client(IPAddress, port);

		int index = 1;

		//
		if (length < index + 8)
		{
			return;
		}

		ByteBuffer bb = ByteBuffer.wrap(data, index, 8);
		client.random1 = bb.getLong();

		index += 8;

		bb = ByteBuffer.wrap(data, index, 4);
		int pubkeyLength = bb.getInt();
		if (length < index + 4)
		{
			return;
		}
		index += 4;

		byte[] pubkey = new byte[pubkeyLength];
		if (length < index + pubkeyLength)
		{
			System.out.println("handleloginmsg: pubkeyLength exceed the buffer size");
			return;
		}
		System.arraycopy(data, index, pubkey, 0, pubkeyLength);
		index += pubkeyLength;
		client.pubKey = pubkey;

		client.setPublicCipher(pubkey);

		if (!Utility.checkSignature(data, index, client.publicDecodeCipher))
		{
			System.out.println("handleLoginMsg: signature checking failed");
			return;
		}

		client.random2 = rand.nextLong();

		addClient(client, 1);
		sendLoginChallenge(client);
	}

	// send LogininClallenge message
	void sendLoginChallenge(Client client)
	{
		byte[] data = new byte[4096];
		int index = 1;
		data[0] = 1;

		byte[] rand1 = ByteBuffer.allocate(8).putLong(client.random1).array();
		System.arraycopy(rand1, 0, data, index, 8);
		index += 8;

		byte[] rand2 = ByteBuffer.allocate(8).putLong(client.random2).array();
		System.arraycopy(rand2, 0, data, index, 8);
		index += 8;

		// to dao, add MAC
		try
		{
			Utility.insertSignature(data, index, this.encodePrivateCipher);

			byte[] cypherdata = Utility.blockCipher(data, Cipher.ENCRYPT_MODE, client.publicEncodeCipher);

			byte[] cType = Utility.createCipheredMsgType(MessageType.loginChallenge, client.publicEncodeCipher);
			byte[] sendData = Utility.combineTwoByteArray(cType, cypherdata);
			DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, client.IPAddress, client.port);

			serverSocket.send(sendPacket);
		} catch (Exception e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		client.status = Client.ClientStatus.WaitLoginChallenge;
		System.out.println("send sendloginChallenge msg");

	}

	// handle LoginResponse Message 
	void handelLoginResponseMsg(byte[] d, DatagramPacket receivePacket)
	{
		System.out.println("handle loginResp msg");
		byte[] data = d;
		int length = data.length;

		InetAddress IPAddress = receivePacket.getAddress();
		Integer port = receivePacket.getPort();

		Client client = getClient(IPAddress, 1);

		// if the client already in the client list;
		if (client == null || client.status != Client.ClientStatus.WaitLoginChallenge)
		{
			return;
		}

		int index = 1;
		//
		if (length < index + 8)
		{
			System.out.println("handleLoginResponseMsg:  userlength is over the buffer size");
			return;
		}

		ByteBuffer bb = ByteBuffer.wrap(data, index, 4);
		int userLength = bb.getInt();

		index += 4;

		if (length < index + userLength)
		{
			System.out.println("handleLoginResponseMsg: user is over the buffersize");
			return;
		}
		byte[] user = new byte[userLength];
		System.arraycopy(data, index, user, 0, userLength);
		index += userLength;
		client.name = new String(user);

		if (length < index + 4)
		{
			System.out.println("handleLoginResponseMsg:  hashlength is over the buffer size");
			return;
		}
		bb = ByteBuffer.wrap(data, index, 4);
		int hashLength = bb.getInt();
		if (length < index + hashLength)
		{
			System.out.println("handleLoginResponseMsg:  hash is over the buffer size");
			return;
		}
		index += 4;

		byte[] hash = new byte[hashLength];
		System.arraycopy(data, index, hash, 0, hashLength);
		index += hashLength;

		String passhash = usrpassword.get(client.name);
		if (passhash == null)
		{
			System.out.println("handleLoginResponseMsg:  password hash is null");
			return;
		}

		if (!Utility.checkSignature(data, index, client.publicDecodeCipher))
		{
			System.out.println("handleLoginResponseMsg: signature checking failed");
			return;
		}

		try
		{
			Long random = client.random1 + client.random2;
			MessageDigest md = MessageDigest.getInstance("SHA-256");
			String text = client.name + new String(Utility.hexStringToByteArray(passhash)) + random.toString();
			md.update(text.getBytes());
			byte[] hash2 = md.digest();
			if (!new String(hash).equals(new String(hash2)))
			{
				System.out.println("handelLoginResponseMsg: user " + client.name + "'s password is not right");
				return;
			}

		} catch (NoSuchAlgorithmException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		sendLoginAck(client);
	}

	// send LoginAck message
	void sendLoginAck(Client client)
	{
		byte[] data = new byte[4096];
		int index = 1;
		data[0] = 1;

		long random = client.random1 + client.random2;
		byte[] rand1 = ByteBuffer.allocate(8).putLong(random).array();
		System.arraycopy(rand1, 0, data, index, 8);
		index += 8;

		// set p
		byte[] p = p512.toByteArray();
		byte[] pLength = ByteBuffer.allocate(4).putInt(p.length).array();
		System.arraycopy(pLength, 0, data, index, 4);
		index += 4;
		System.arraycopy(p, 0, data, index, p.length);
		index += p.length;

		// set g
		byte[] g = g512.toByteArray();
		byte[] gLength = ByteBuffer.allocate(4).putInt(g.length).array();
		System.arraycopy(gLength, 0, data, index, 4);
		index += 4;
		System.arraycopy(g, 0, data, index, g.length);
		index += g.length;

		// set the g^a mod p
		client.setDHKeyPair(p512, g512);

		try
		{

			Utility.insertSignature(data, index, this.encodePrivateCipher);

			byte[] cypherdata = Utility.blockCipher(data, Cipher.ENCRYPT_MODE, client.publicEncodeCipher);

			byte[] cType = Utility.createCipheredMsgType(MessageType.loginAck, client.publicEncodeCipher);
			byte[] sendData = Utility.combineTwoByteArray(cType, cypherdata);

			DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, client.IPAddress, client.port);

			serverSocket.send(sendPacket);
		} catch (Exception e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		client.status = Client.ClientStatus.WaitLoginSessionStart;
		System.out.println("send loginAck msg");
	}

	// handle loginSessionStart message
	void handelLoginSessionStartMsg(byte[] d, DatagramPacket receivePacket)
	{

		System.out.println("handle sessionStart msg");

		byte[] data = d;
		int length = data.length;

		InetAddress IPAddress = receivePacket.getAddress();
		Integer port = receivePacket.getPort();

		Client client = getClient(IPAddress, 1);

		// if no client with such ip or the client is not waiting to the
		// corresponding msg
		if (client == null || client.status != Client.ClientStatus.WaitLoginSessionStart)
		{
			System.out.println("no client with such ip or the client is not waiting to the corresponding msg");
			return;
		}

		int index = 1;
		//
		if (length < index + 8)
		{
			return;
		}

		ByteBuffer bb = ByteBuffer.wrap(data, index, 8);
		long random = bb.getLong();

		index += 8;

		if (random != client.random1 + client.random2)
		{
			System.out.println("the random num is not right");
		}

		if (length < index + 4)
		{
			return;
		}
		bb = ByteBuffer.wrap(data, index, 4);
		int modLength = bb.getInt();
		index += 4;

		if (length < index + modLength)
		{
			return;
		}
		byte[] mod = new byte[modLength];
		System.arraycopy(data, index, mod, 0, modLength);
		index += modLength;

		if (!Utility.checkSignature(data, index, client.publicDecodeCipher))
		{
			System.out.println("handleLoginResponseMsg: signature checking failed");
			return;
		}

		try
		{

			DHPublicKey cliServPub = (DHPublicKey) KeyFactory.getInstance("DH").generatePublic(new X509EncodedKeySpec(mod));

			KeyAgreement serverKA = KeyAgreement.getInstance("DH");
			serverKA.init(client.DHKeyPair.getPrivate(), new DHParameterSpec(p512, g512));
			serverKA.doPhase(cliServPub, true);
			client.cliSerKey = serverKA.generateSecret();

			client.initAESCipher(client.cliSerKey);

		} catch (Exception e)
		{
			e.printStackTrace();
		}

		sendLoginSessionStartAck(client);
	}

	// send loginSessionStartAck message
	void sendLoginSessionStartAck(Client client)
	{
		byte[] data = new byte[4096];
		int index = 1;
		data[0] = 1;

		long random = client.random1 + client.random2;
		byte[] rand1 = ByteBuffer.allocate(8).putLong(random).array();
		System.arraycopy(rand1, 0, data, index, 8);
		index += 8;

		byte[] serverpk = client.DHKeyPair.getPublic().getEncoded();
		byte[] pkLength = ByteBuffer.allocate(4).putInt(serverpk.length).array();
		System.arraycopy(pkLength, 0, data, index, 4);
		index += 4;
		System.arraycopy(serverpk, 0, data, index, serverpk.length);
		index += serverpk.length;

		try
		{

			Utility.insertSignature(data, index, this.encodePrivateCipher);

			byte[] cypherdata = Utility.blockCipher(data, Cipher.ENCRYPT_MODE, client.publicEncodeCipher);
			byte[] cType = Utility.createCipheredMsgType(MessageType.loginSessionStartAck, client.publicEncodeCipher);
			byte[] sendData = Utility.combineTwoByteArray(cType, cypherdata);

			DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, client.IPAddress, client.port);

			serverSocket.send(sendPacket);
		} catch (Exception e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		System.out.println("send loginSessionStartAck msg");

		client.status = Client.ClientStatus.Connnected;
		removeClient(client.IPAddress, 1);
		addClient(client, 2);
	}

	// handle ListRequest message
	public void handelListRequst(byte[] d, DatagramPacket receivePacket)
	{
		System.out.println("handle ListRequest msg");
		byte[] data = d;
		int length = data.length;

		InetAddress IPAddress = receivePacket.getAddress();
		Integer port = receivePacket.getPort();

		Client client = getClient(IPAddress, 2);

		// if no client with such ip or the client is not waiting to the
		// corresponding msg
		if (client == null)
		{
			return;
		}

		int index = 1;
		//
		if (length < index + 32)
		{
			return;
		}

		ByteBuffer bb = ByteBuffer.wrap(data, index, 8);
		client.random1 = bb.getLong();
		index += 8;

		bb = ByteBuffer.wrap(data, index, 4);
		int usrLength = bb.getInt();
		index += 4 + usrLength;

		if (!Utility.checkSignature(data, index, client.publicDecodeCipher))
		{
			System.out.println("handleListRequestMsg: signature checking failed");
			return;
		}

		sendlListResp(client);
	}

	// send ListResp message
	void sendlListResp(Client client)
	{
		byte[] data = new byte[2048];
		int index = 1;
		data[0] = 1;

		long random1 = client.random1;
		byte[] rand1 = ByteBuffer.allocate(8).putLong(random1).array();
		System.arraycopy(rand1, 0, data, index, 8);
		index += 8;

		client.random2 = rand.nextLong();
		long random2 = client.random2;
		byte[] rand2 = ByteBuffer.allocate(8).putLong(random2).array();
		System.arraycopy(rand2, 0, data, index, 8);
		index += 8;

		byte[] timeStamp = ByteBuffer.allocate(8).putLong(new Date().getTime()).array();
		System.arraycopy(timeStamp, 0, data, index, 8);
		index += 8;

		int clientCount = establishedClients.size();
		byte[] length = ByteBuffer.allocate(4).putInt(clientCount).array();
		System.arraycopy(length, 0, data, index, 4);

		index += 4;
		for (int i = 0; i < clientCount; i++)
		{
			String clientName = establishedClients.get(i).name;
			int nameLength = clientName.length();

			length = ByteBuffer.allocate(4).putInt(nameLength).array();
			System.arraycopy(length, 0, data, index, 4);
			index += 4;

			System.arraycopy(clientName.getBytes(), 0, data, index, nameLength);
			index += nameLength;
		}

		try
		{

			Utility.insertSignature(data, index, this.encodePrivateCipher);

			byte[] cypherdata = client.AESEncodeCipher.doFinal(data);
			byte[] cType = Utility.createCipheredMsgType(MessageType.listResp, client.publicEncodeCipher);
			byte[] sendData = Utility.combineTwoByteArray(cType, cypherdata);

			DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, client.IPAddress, client.port);

			serverSocket.send(sendPacket);
		} catch (Exception e)
		{
			// TODO Auto-generated catch block

			e.printStackTrace();
		}

		System.out.println("send ListResp msg");
		client.status = Client.ClientStatus.Connnected;
	}

	// handle Send Message
	public void handleSendMsg(byte[] d, DatagramPacket receivePacket)
	{

		System.out.println("handle Send Msg");
		byte[] data = d;
		int length = data.length;

		InetAddress IPAddress = receivePacket.getAddress();
		Integer port = receivePacket.getPort();

		Client client = getClient(IPAddress, 2);

		// if no client with such ip or the client is not waiting to the
		// corresponding msg
		if (client == null)
		{
			return;
		}

		int index = 1;

		//
		if (length < index + 32)
		{
			return;
		}

		ByteBuffer bb = ByteBuffer.wrap(data, index, 4);
		int ALength = bb.getInt();
		index += 4;

		if (length < index + ALength + 4)
		{
			return;
		}
		byte[] userA = new byte[ALength];
		System.arraycopy(data, index, userA, 0, ALength);
		index += ALength;

		bb = ByteBuffer.wrap(data, index, 4);
		int BLength = bb.getInt();
		index += 4;
		if (length < index + BLength + 8)
		{
			return;
		}
		byte[] userB = new byte[BLength];
		System.arraycopy(data, index, userB, 0, BLength);
		index += BLength;

		Client clientB = getEstablishedClientFromByName(new String(userB));
		if (clientB == null)
		{
			return;
		}

		bb = ByteBuffer.wrap(data, index, 8);
		long random1 = bb.getLong();
		index += 8;

		if (!Utility.checkSignature(data, index, client.publicDecodeCipher))
		{
			System.out.println("handleSendMsg: signature checking failed");
			return;
		}

		sendSendReqMsg(random1, userA, userB, client, clientB);
	}

	// send sendreq message
	void sendSendReqMsg(long rand1, byte[] userA, byte[] userB, Client clientA, Client clientB)
	{
		byte[] data = new byte[2048];
		int index = 1;
		data[0] = 1;

		byte[] lengthA = ByteBuffer.allocate(4).putInt(userA.length).array();
		System.arraycopy(lengthA, 0, data, index, 4);
		index += 4;

		System.arraycopy(userA, 0, data, index, userA.length);
		index += userA.length;

		byte[] lengthB = ByteBuffer.allocate(4).putInt(userB.length).array();
		System.arraycopy(lengthB, 0, data, index, 4);
		index += 4;

		System.arraycopy(userB, 0, data, index, userB.length);
		index += userB.length;

		byte[] random1 = ByteBuffer.allocate(8).putLong(rand1).array();
		System.arraycopy(random1, 0, data, index, 8);
		index += 8;

		byte[] pubkey = clientA.pubKey;
		byte[] lengthKey = ByteBuffer.allocate(4).putInt(pubkey.length).array();
		System.arraycopy(lengthKey, 0, data, index, 4);
		index += 4;

		System.arraycopy(pubkey, 0, data, index, pubkey.length);
		index += pubkey.length;

		Utility.insertSignature(data, index, this.encodePrivateCipher);

		try
		{
			byte[] cypherdata = clientB.AESEncodeCipher.doFinal(data);

			byte[] cType = Utility.createCipheredMsgType(MessageType.sendReq, clientB.publicEncodeCipher);
			byte[] sendData = Utility.combineTwoByteArray(cType, cypherdata);

			DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, clientB.IPAddress, clientB.port);

			serverSocket.send(sendPacket);
			int n = 4;
		} catch (Exception e)
		{
			// TODO Auto-generated catch block

			e.printStackTrace();
		}

		System.out.println("send sendReq msg");
	}

	// handle sendReqAck Message
	public void handleSendReqAckMsg(byte[] d, DatagramPacket receivePacket)
	{
		System.out.println("handle sendReqAck msg");
		byte[] data = d;
		int length = data.length;

		InetAddress IPAddress = receivePacket.getAddress();

		Client clientB = getClient(IPAddress, 2);

		// if no client with such ip or the client is not waiting to the
		// corresponding msg
		if (clientB == null)
		{
			System.out.println("HandleSendReqAckMsg: client B doesn't exist");
			return;
		}

		int index = 1;
		//
		if (length < index + 32)
		{
			return;
		}

		ByteBuffer bb = ByteBuffer.wrap(data, index, 4);
		int ALength = bb.getInt();
		index += 4;

		if (length < index + ALength + 4)
		{
			return;
		}
		byte[] userA = new byte[ALength];
		System.arraycopy(data, index, userA, 0, ALength);
		index += ALength;

		bb = ByteBuffer.wrap(data, index, 4);
		int BLength = bb.getInt();
		index += 4;
		if (length < index + BLength + 8)
		{
			return;
		}
		byte[] userB = new byte[BLength];
		System.arraycopy(data, index, userB, 0, BLength);
		index += BLength;

		Client clientA = getEstablishedClientFromByName(new String(userA));
		if (clientA == null)
		{
			return;
		}

		bb = ByteBuffer.wrap(data, index, 8);
		long random2 = bb.getLong();
		index += 8;

		if (!Utility.checkSignature(data, index, clientB.publicDecodeCipher))
		{
			System.out.println("handleSendReqAckMsgMsg: signature checking failed");
			return;
		}

		sendSendRespAcMsg(random2, userA, userB, clientA, clientB);
	}

	// send sendRespAck Message
	void sendSendRespAcMsg(long rand2, byte[] userA, byte[] userB, Client clientA, Client clientB)
	{
		byte[] data = new byte[2048];
		int index = 1;
		data[0] = 1;

		byte[] lengthA = ByteBuffer.allocate(4).putInt(userA.length).array();
		System.arraycopy(lengthA, 0, data, index, 4);
		index += 4;

		System.arraycopy(userA, 0, data, index, userA.length);
		index += userA.length;

		byte[] lengthB = ByteBuffer.allocate(4).putInt(userB.length).array();
		System.arraycopy(lengthB, 0, data, index, 4);
		index += 4;

		System.arraycopy(userB, 0, data, index, userB.length);
		index += userB.length;

		byte[] random2 = ByteBuffer.allocate(8).putLong(rand2).array();
		System.arraycopy(random2, 0, data, index, 8);
		index += 8;

		String ip = clientB.IPAddress.getHostAddress();
		byte[] ipB = clientB.IPAddress.getHostAddress().getBytes();
		byte[] ipLength = ByteBuffer.allocate(4).putInt(ipB.length).array();
		System.arraycopy(ipLength, 0, data, index, 4);
		index += 4;

		System.arraycopy(ipB, 0, data, index, ipB.length);
		index += ipB.length;

		byte[] pubkey = clientB.pubKey;
		byte[] lengthKey = ByteBuffer.allocate(4).putInt(pubkey.length).array();
		System.arraycopy(lengthKey, 0, data, index, 4);
		index += 4;

		System.arraycopy(pubkey, 0, data, index, pubkey.length);
		index += pubkey.length;

		Utility.insertSignature(data, index, this.encodePrivateCipher);

		try
		{
			byte[] cypherdata = clientA.AESEncodeCipher.doFinal(data);
			byte[] cType = Utility.createCipheredMsgType(MessageType.sendRspAck, clientA.publicEncodeCipher);
			byte[] sendData = Utility.combineTwoByteArray(cType, cypherdata);

			DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, clientA.IPAddress, clientA.port);

			serverSocket.send(sendPacket);
			int n = 4;
		} catch (Exception e)
		{
			// TODO Auto-generated catch block

			e.printStackTrace();
		}

		System.out.println("send sendResp msg");
	}

	// send broadLogOff message
	public void sendBroadLogOff(Client client, String username)
	{
		System.out.println("sendBroadLogOff for " + username);
		byte[] cType = Utility.createCipheredMsgType(MessageType.broadcastLogoff, client.publicEncodeCipher);
		byte[] data = new byte[2048];

		int index = 1;
		data[0] = 1;

		int userLength = username.length();
		byte[] length = ByteBuffer.allocate(4).putInt(userLength).array();
		System.arraycopy(length, 0, data, index, 4);
		index += 4;
		byte[] u = username.getBytes();
		System.arraycopy(u, 0, data, index, userLength);
		index += userLength;

		try
		{

			Utility.insertSignature(data, index, this.encodePrivateCipher);

			byte[] cypherdata = client.AESEncodeCipher.doFinal(data);
			byte[] sendData = Utility.combineTwoByteArray(cType, cypherdata);
			DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, client.IPAddress, client.port);
			serverSocket.send(sendPacket);

		} catch (Exception e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	// handle logoff message
	public void handleLogoff(byte[] d, DatagramPacket receivePacket)
	{
		System.out.println("handleBroadcastLogoff");

		byte[] data = d;
		int length = data.length;
		int index = 1;

		if (length < index + 4)
		{
			return;
		}

		ByteBuffer bb = ByteBuffer.wrap(data, index, 4);
		int userALength = bb.getInt();
		index += 4;
		if (length < index + userALength)
		{
			return;
		}
		byte[] userA = new byte[userALength];
		System.arraycopy(data, index, userA, 0, userALength);

		index += userALength;

		Client client = this.getClient(receivePacket.getAddress(), 2);

		if (client == null)
		{
			System.out.println("handleLogoff: no client with the given ip");
		}

		if (!Utility.checkSignature(data, index, client.publicDecodeCipher))
		{
			System.out.println("handleLogOffmsg: signature checking failed");
			return;
		}

		if (client != null)
		{
			this.removeClient(client.IPAddress, 2);
			for (int i = 0; i < this.establishedClients.size(); i++)
			{
				this.sendBroadLogOff(establishedClients.get(i), new String(userA));
			}
		}
	}

	Client getEstablishedClientFromByName(String name)
	{
		synchronized (lock2)
		{
			for (int i = 0; i < establishedClients.size(); i++)
			{
				if (establishedClients.get(i).name.equals(name))
				{
					return establishedClients.get(i);
				}
			}
		}

		return null;
	}

	// handle the request
	public void HandleRequest(DatagramPacket receivePacket)
	{

		byte[] cipherdata = receivePacket.getData();
		byte[] data = null;
		byte[] receiveData = receivePacket.getData();

		if (receivePacket.getLength() < 260)
		{
			System.out.println("Listen Request: the size of receving packet is too small");
			return;
		}
		byte[] cType = new byte[128];
		System.arraycopy(receiveData, 0, cType, 0, 128);
		byte[] msgType;
		try
		{
			msgType = this.decodePrivateCipher.doFinal(cType);
		} catch (Exception e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
			return;
		}

		int type = msgType[0];

		try
		{
			Client client = getClient(receivePacket.getAddress(), 2);

			int length = receivePacket.getLength() - 128;
			byte[] rawData = new byte[length];
			System.arraycopy(receivePacket.getData(), 128, rawData, 0, length);

			if (type <= MessageType.loginSessionStartAck)
			{
				data = Utility.blockCipher(rawData, Cipher.DECRYPT_MODE, decodePrivateCipher);
			} else
			{

				if (client == null)
				{
					System.out.println("receive " + type + " message, but the client is null");
				}
				data = client.AESDecodeCipher.doFinal(rawData, 0, rawData.length);
			}
		} catch (IllegalBlockSizeException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (BadPaddingException e)
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
			return;
		}

		if (type == MessageType.login)
		{
			handelLoginMsg(data, receivePacket);
			return;
		}

		if (type == MessageType.loginResponse)
		{
			handelLoginResponseMsg(data, receivePacket);
			return;
		}

		if (type == MessageType.loginSessionStart)
		{
			handelLoginSessionStartMsg(data, receivePacket);
			return;
		}

		if (type == MessageType.listReq)
		{
			handelListRequst(data, receivePacket);
			return;
		}

		if (type == MessageType.send)
		{
			handleSendMsg(data, receivePacket);
			return;
		}

		if (type == MessageType.sendReqAck)
		{
			handleSendReqAckMsg(data, receivePacket);
			return;
		}

		if (type == MessageType.logoff)
		{
			this.handleLogoff(data, receivePacket);
			return;
		}
	}

	// monitor the request
	public void ListenRequest()
	{
		while (true)
		{
			byte[] receiveData = new byte[4096];
			byte[] sendData = new byte[4096];

			DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
			try
			{
				serverSocket.receive(receivePacket);
			} catch (IOException e)
			{
				// TODO Auto-generated catch block
				e.printStackTrace();
				return;
			}
			System.out.println("receive request");
			final DatagramPacket packet = receivePacket;
			Thread t = new Thread(new Runnable()
			{

				public void run()
				{

					HandleRequest(packet);

				}
			});

			t.start();

		}
	}

	public static void main(String args[]) throws Exception
	{

		File privateKeyFile = new File("serverprivate.key");
		DataInputStream in = new DataInputStream(new FileInputStream(privateKeyFile));
		byte[] privateKey = new byte[(int) privateKeyFile.length()];
		in.readFully(privateKey);

		Security.addProvider(new BouncyCastleProvider());

		File configFile = new File("serverConfig.txt");
		in = new DataInputStream(new FileInputStream(configFile));
		String line = in.readLine();
		int port;

		if (line.contains("port"))
		{
			port = Integer.valueOf(line.split(":")[1]);
		} else
		{
			System.out.println("need set the port");
			return;
		}

		File passFile = new File("password.txt");
		in = new DataInputStream(new FileInputStream(passFile));
		HashMap<String, String> usrpsw = new HashMap<String, String>();

		while ((line = in.readLine()) != null)
		{
			String[] tokens = line.split(" ");
			if (tokens.length == 2)
			{
				usrpsw.put(tokens[0], tokens[1]);
			}
		}

		System.out.println("start");
		InstantMessageServer ins = new InstantMessageServer(port, privateKey, usrpsw);
		ins.ListenRequest();

	}

}